package com.szz.config;

import com.szz.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/*
    SpringSecurity的配置类
        可以配置通过内存或通过UserDetails(数据库)对象来加载用户信息
        可以配置拦截和放行的地址(授权操作)，也可以配置url的授权
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        /*
            1. 编写实现类，实现UserDetailsService接口，重写loadUserByUsername方法，返回UserDetails对象
                是否选择使用MybatisPlus，大家自己选择
            2. 编写User实现类，实现UserDetails接口
                重写几个方法
                    getUsername 用户名称，通过Security上下文可以查询到，这里最好返回userId，方便通过主键直接查询
                    getPassword 密码，返回数据库中查询出的密码，框架需要通过该方法获取BCrypt加密后的密码进行比对
                下面四个方法，但凡有一个返回false，用户是可以查询到的，但是无法登录
                    isAccountNonExpired 用户账号是否过期，根据status进行判断
                    isAccountNonLocked 用户账号是否锁定，根据status进行判断
                    isCredentialsNonExpired 用户凭证是否过期，根据status进行判断
                    isEnabled 用户是否可用，根据status进行判断
                    getAuthorities 授权的方法，通过用户名查询出的用户对象，再根据用户id查询出权限信息，封装的地方
            3. 在loadUserByUsername方法，通过业务实现用户的查询和授权的一系列操作，并返回User对象
                如果没问题，授权完成，返回User对象
                如果有问题，直接返回null，即可
         */
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    /**
     * 将自定义的拒绝访问处理器注入进来
     */
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    /**
     * 将自定义的登录成功处理器注入进来
     */
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    /**
     * 将自定义的登录失败处理器注入进来
     */
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

//    /**
//     * 配置用户信息，模拟内存用户数据
//     *
//     * @param auth
//     * @throws Exception
//     */
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 在内存中配置两个用户
//        auth.inMemoryAuthentication()
//                .withUser("cxs")
//                .password(passwordEncoder().encode("123"))
//                .roles("ADMIN")
//                .authorities("sys:save", "sys:del", "sys:update", "sys:query") // 给cxs用户添加四个权限
//                .and()
//                .withUser("test")
//                .password(passwordEncoder().encode("123"))
//                .roles("TEST")
//                .authorities("sys:save", "sys:query") // 给test这个用户加两个权限
//                .and()
//                .withUser("admin")
//                .password(passwordEncoder().encode("123"))
//                .roles("ADMIN");  // 给admin这个用户一个ADMIN的角色，如果角色和权限都给了，那么角色就失效了
//    }

    /*
        认证相关操作
            基于内存配置
            基于数据库查询配置
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于认证的配置信息
        auth
                //基于数据库配置用户信息
                .userDetailsService(userDetailsService)
                .and()
                //基于内存配置用户信息
                .inMemoryAuthentication()
                //配置用户相关信息
                //配置用户几名
                .withUser("admin")
                //配置用户密码，Security框架要求这个密码必须以BCrypt方式进行加密
                /*
                    BCrypt是一种加密器，加密算法，该算法也是不可逆的
                        MD5：不可逆的加密算法，通过固定字符串生成的MD5值是一致的
                            通过用户名和密码查询用户
                        BCrypt：不可逆的加密算法，通过固定字符串生成的密码是不一致的
                            通过用户名查询用户，然后通过BCrypt的api来匹配密码是否合法
                 */
                .password(passwordEncoder().encode("123"))
                //配置用户的角色信息，如果没有权限信息，那么角色信息就是我们的权限信息
                //配置的用户信息，自动添加ROLE_前缀，不要以ROLE_开头命名，因为是自动添加的
                //.roles("ADMIN")
                //配置用户的权限信息，如果有角色信息和权限信息，以权限信息为主
                .authorities("user:save","user:delete","user:update","user:query","goods:save","goods:delete","goods:update","goods:query")
                //配置多用户信息
                .and()
                .withUser("seller")
                .password(passwordEncoder().encode("123"))
                .authorities("goods:save","goods:delete","goods:update","goods:query")
                .and()
                .withUser("test")
                .password(passwordEncoder().encode("123"))
                .roles("TEST")
        ;
    }
//    /**
//     * 配置http请求验证等
//     *
//     * @param http
//     * @throws Exception
//     */
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        // 注释掉他自己的方法 走我们自己的
//        // super.configure(http);
//        // 给一个异常处理器，走我们自定义的拒绝访问处理器
//        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
//        http.formLogin().successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler);
//        // 给一个表单登陆 就是我们的登录页面,登录成功或者失败后走我们的url
//        http.formLogin().successForwardUrl("/welcome").failureForwardUrl("/fail");
//        // 匹配哪些url，需要哪些权限才可以访问 当然我们也可以使用链式编程的方式
//        http.authorizeRequests()
////                .antMatchers("/query").hasAnyAuthority("sys:query")
////                .antMatchers("/save").hasAnyAuthority("sys:save")
////                .antMatchers("/del").hasAnyAuthority("sys:del")
////                .antMatchers("/update").hasAnyAuthority("sys:update")
////                .antMatchers("/admin/**").hasRole("ADMIN")
//                .anyRequest().authenticated(); // 其他所有的请求都需要登录才能进行
//    }

    /*
     * 从 Spring5 开始，强制要求密码要加密
     * 如果非不想加密，可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder，
     * 但是不建议这么做，毕竟不安全。
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}